#!/usr/bin/env python
# -*- coding: utf-8 -*-
# @Time    : 2021/4/22 23:38
# @Author  : Grayson Liu
# @Email   : graysonliu@foxmail.com
# @File    : CalculateAccuracyDialog.py
import threading

from PyQt5.QtWidgets import QDialog, QFileDialog
from osgeo import gdal
from sklearn.metrics import confusion_matrix

from ui.Ui_CalculateAccuracyDialog import Ui_CalculateAccuracyDialog

import numpy as np

from CustomSignal import CustomSignal


class CalculateAccuracyDialog(QDialog, Ui_CalculateAccuracyDialog):
    """
    计算精度对话框
    """
    global_signal = CustomSignal()

    def __init__(self, current_data: np.ndarray, parent=None):
        """
        初始化
        """
        super(CalculateAccuracyDialog, self).__init__(parent)
        self.setupUi(self)
        # 成员变量
        self.current_data = current_data
        self.path = ""

        # 将“计算精度”按钮设为不可选
        self.btnCalculate.setEnabled(False)

        # 绑定槽和函数
        self.btnBrowse.clicked.connect(self.browse_file)
        self.btnCalculate.clicked.connect(self.calculate_accuracy)
        self.btnConfirm.clicked.connect(lambda: self.close())

        CalculateAccuracyDialog.global_signal.update_text.connect(self.show_result)

    def browse_file(self):
        """
        对话框”浏览“按钮的槽函数
        :return:
        """
        # 打开文件浏览器，选择输出图像的保存位置
        path, type = QFileDialog.getOpenFileName(self, "选择真实边界数据",
                                                 filter="GeoTiff Files(*.tif)")
        if path == "":
            return
        else:
            self.txtPath.setText(path)
            self.path = path
            self.btnCalculate.setEnabled(True)

    def calculate_accuracy(self):
        """
        对话框“计算精度”按钮的槽函数，计算精度，显示在当前窗体上
        :return:
        """

        def read_tif(path: str) -> np.ndarray:
            """
            读tif数据，用于读取真实边界数据
            :param path: 真实边界数据路径
            :return: 返回np.ndarray的数组
            """
            dataset = gdal.Open(path)
            if dataset is None:
                return None
            im_width = dataset.RasterXSize  # 栅格矩阵的列数
            im_height = dataset.RasterYSize  # 栅格矩阵的行数
            im_data = dataset.ReadAsArray(0, 0, im_width, im_height)  # 获取数据
            return im_data

        def thread_calculate_accuracy(real_edge_path: str, current_data: np.ndarray):
            """
            线程体，计算精度
            :param real_edge_path: 真实边界数据路径
            :param current_data: 当前处理完成的图像矩阵
            :return:
            """
            result_str = ""
            # 计算当前图像基本信息
            max_value = current_data.max()
            min_value = current_data.min()
            mean_value = current_data.mean()
            std_value = current_data.std()
            result_str += "<b>图像基本信息：</b><br>最大值：{:.4f}<br>最小值：{:.4f}<br>均值：{:.4f}<br>标准差：{:.4f}<hr>".format(
                max_value,
                min_value,
                mean_value,
                std_value,
            )
            # 计算与真实边界数据的对比结果，显示混淆矩阵和OA及Kappa
            real_edge = read_tif(real_edge_path)
            if real_edge is None:
                CalculateAccuracyDialog.global_signal.update_text.emit("真实边界数据有误，请重新选择。")
                return
            # 同时变成0-1数据
            real_edge = real_edge.flatten()
            predict_edge = self.current_data.copy()
            predict_edge = predict_edge.flatten()
            # 计算混淆矩阵
            tn, fp, fn, tp = confusion_matrix(real_edge, predict_edge).ravel()
            result_str += "<b>混淆矩阵：</b><br>TN = {:<10d} FP = {:<10d} <br>FN = {:<10d} TP = {:<10d}<hr>".format(
                tn, fp, fn, tp
            )
            # 计算Kappa和OA
            n = tp + fp + fn + tn
            Po = (tp + tn) / n
            Pe = ((tp + fn) * (tp + fp) + (fp + tn) * (fn + tn)) / (n * n)
            kappa = (Po - Pe) / (1 - Pe)
            OA = (tp + tn) / n
            result_str += "<b>精度：</b><br>Kappa = {:.6f}<br>OA = {:.6f}<hr>".format(
                kappa, OA
            )
            # 给Ui线程发信号，显示精度计算结果
            CalculateAccuracyDialog.global_signal.update_text.emit(result_str)

        real_edge_path = self.txtPath.text()
        self.txtCalculateResult.setText("开始计算精度...")
        # 点击按钮后，启动一个线程去完成精度计算的操作
        thread = threading.Thread(target=thread_calculate_accuracy, args=(real_edge_path, self.current_data))
        thread.start()

    def show_result(self, result_str: str):
        """
        自定义信号update_text的槽函数，显示精度计算的结果
        :return:
        """
        self.txtCalculateResult.setText(result_str)
